using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Interfaces;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;

namespace FernUniHagen.CompilerExtension.Adaptor
{
    public abstract class BlockNodeFactory : IMultiplicityConvertible<CommonSyntaxNode>
    {
        #region members
        private ISemanticModel _semanticModel;
        private BlockSyntax _blockSyntax;
        private SyntaxNodeInfo _nodeContext;
        #endregion

        #region properties
        protected ISemanticModel SemanticModel
        {
            get { return _semanticModel; }
            set { _semanticModel = value; }
        }
        protected BlockSyntax BlockSyntax
        {
            get { return _blockSyntax; }
            set { _blockSyntax = value; }
        }
        protected SyntaxNodeInfo NodeContext {
            get { return _nodeContext; }
            set { _nodeContext = value; }
        }
        //}
        #endregion


        public static IMultiplicityConvertible<CommonSyntaxNode> GetConverter(ISemanticModel semanticModel, BlockSyntax blockSyntax, SyntaxNodeInfo nodeToReplaceInBlock)
        {

            BlockNodeFactory convertibleNode = null;

            if (nodeToReplaceInBlock.ReferencedNode is BinaryExpressionSyntax)
            {
                var bin = (BinaryExpressionSyntax)nodeToReplaceInBlock.ReferencedNode;
                //Save Expression for R-Mult rules (^field^)
                if (bin.Kind == SyntaxKind.ExclusiveOrExpression && bin.Parent.Kind == SyntaxKind.ExclusiveOrExpression)
                {
                    convertibleNode = new MultExpressionAdapter(bin);
                }
                else
                {
                    convertibleNode = new BinaryExpressionAdapter(bin);

                }
            }
            else if (nodeToReplaceInBlock.ReferencedNode is ElementAccessExpressionSyntax)
                convertibleNode = new ElementAccessExpressionAdapter(nodeToReplaceInBlock.ReferencedNode as ElementAccessExpressionSyntax);
            else if (nodeToReplaceInBlock.ReferencedNode is MemberAccessExpressionSyntax)
                convertibleNode = new MemberAccessExpressionAdapter(nodeToReplaceInBlock.ReferencedNode as MemberAccessExpressionSyntax);
            else if (nodeToReplaceInBlock.ReferencedNode is InvocationExpressionSyntax)
                convertibleNode = new InvocationExpressionAdapter(nodeToReplaceInBlock.ReferencedNode as InvocationExpressionSyntax);
            else if (nodeToReplaceInBlock.Statement is ReturnStatementSyntax)
                convertibleNode = new ReturnStatementAdapter(nodeToReplaceInBlock.Statement as ReturnStatementSyntax);
            else if (nodeToReplaceInBlock.ReferencedNode is CastExpressionSyntax)
            { 
                convertibleNode = new CastExpressionAdapter(nodeToReplaceInBlock.ReferencedNode as CastExpressionSyntax);
            }
            else if (nodeToReplaceInBlock.ReferencedNode is QueryExpressionSyntax)
                convertibleNode = new QueryExpressionAdaptor(nodeToReplaceInBlock.ReferencedNode as QueryExpressionSyntax);
            else if (nodeToReplaceInBlock.ReferencedNode is ArgumentSyntax)
                convertibleNode = new DefaultBlockNode(nodeToReplaceInBlock.ReferencedNode);
            else if (nodeToReplaceInBlock.ReferencedNode is IdentifierNameSyntax)
                convertibleNode = new IdentifierSyntaxAdapter(nodeToReplaceInBlock.ReferencedNode as IdentifierNameSyntax);
            else
            {
                convertibleNode = new DefaultBlockNode(nodeToReplaceInBlock.ReferencedNode);
            }
            convertibleNode.SemanticModel = semanticModel;
            convertibleNode.BlockSyntax = blockSyntax;
            convertibleNode.NodeContext = nodeToReplaceInBlock;

            return convertibleNode;
        }

        public static IReferencable GetReference(ISolution solution,ISemanticModel semanticModel, CommonSyntaxToken token)
        {
            IReferencable reference = null;
            //All nodes found as a reference are in a block. This whole block we need to replace, so store it
            BlockSyntax blockSyntax = token.Parent.FirstAncestorOrSelf<BlockSyntax>();
            //Get the document where the node needs to be replaced
            IDocument doc = solution.GetDocument(blockSyntax.SyntaxTree);
            //The expression we want to change is also in a statement, store this so we can find the location in the block we want to replace
            StatementSyntax stmt = token.Parent.FirstAncestorOrSelf<StatementSyntax>();
            //Get the expression which needs to be replaced and where we can apply our rules on
            BinaryExpressionSyntax bin = token.Parent.FirstAncestorOrSelf<BinaryExpressionSyntax>();
            var argument = token.Parent.FirstAncestorOrSelf<ArgumentSyntax>();
            MemberAccessExpressionSyntax mem = token.Parent.FirstAncestorOrSelf<MemberAccessExpressionSyntax>();
            InvocationExpressionSyntax inv = token.Parent.FirstAncestorOrSelf<InvocationExpressionSyntax>();
            CastExpressionSyntax castExpression = token.Parent.FirstAncestorOrSelf<CastExpressionSyntax>();
            QueryExpressionSyntax queryExpression = token.Parent.FirstAncestorOrSelf<QueryExpressionSyntax>();
            IdentifierNameSyntax identifier = token.Parent.FirstAncestorOrSelf<IdentifierNameSyntax>();
            ElementAccessExpressionSyntax elementAccessExpression = token.Parent.FirstAncestorOrSelf<ElementAccessExpressionSyntax>();

            if (argument != null)
            {
                reference = new IdentifierSyntaxAdapter(doc, blockSyntax, identifier, stmt);
            }
            else if (inv != null)
            {
                if(((MemberAccessExpressionSyntax)inv.Expression).Name == identifier)
                    reference = new IdentifierSyntaxAdapter(doc, blockSyntax, identifier, stmt);
                else
                    reference = new InvocationExpressionAdapter(doc, blockSyntax, inv, stmt, identifier);
            }
            else if (elementAccessExpression != null)
            {
                reference = new ElementAccessExpressionAdapter(doc,blockSyntax, elementAccessExpression, stmt,identifier);
            }
            else if (mem != null)
            {
                reference = new MemberAccessExpressionAdapter(doc, blockSyntax, mem, stmt, identifier);
            }
            else if (bin != null /*&& bin.Right != identifier */)
            {
                reference = new BinaryExpressionAdapter(doc, blockSyntax, bin, stmt, identifier);
            }
           

            //This branch covers the normal type cast on annotated elements (R-TCAST)
            else if (castExpression != null)
            {
                reference = new CastExpressionAdapter(doc, blockSyntax, castExpression, stmt, identifier);
            }
            //A querExpression will replace a whole class because we need to implement ienumerable interface. do that here
            else if (queryExpression != null)
            {
                reference = new QueryExpressionAdaptor(doc, blockSyntax, queryExpression, stmt, identifier, semanticModel);
            }
            else
            {
                reference = new IdentifierSyntaxAdapter(doc, blockSyntax, identifier, stmt);
            }

            return reference;
        }

        public abstract StatementReplacement<CommonSyntaxNode> ConvertToMutiplicity(string newVariableName);
    }
}
